using System;
using System.Data;
using System.Configuration;
using System.Web;

using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
using System.IO;
using System.Text;
using System.Threading;


/// <summary>
/// IISTimer ver 1.0.1.2
/// </summary>
namespace Tihnut.Web.Timer
{
    public delegate void TimerCallBack(DateTime time,string TimerName,IISTimer timer);
    public delegate void ExcpetionOccured(Exception exp,string methood);


    public class IISTimer
    {
        long _Interval;
        static object objSync=new Object();
        string _TimerName;
        bool _Infinite;
        bool _Enabled;
        static string _Prefix="Tihnut.com.";
        static string _LogFileName;
        static bool _IsLogEnabled=false;
        internal TimerCallBack _OnTimerCall;
        internal ExcpetionOccured _OnException;
        internal Guid UID = Guid.NewGuid();
        internal string SyncKey;
        bool Destroying = false;
        
        
        public IISTimer()
        {
           
            _TimerName = "Undefined";
            _Infinite = false;
            _Interval = 0;
            _Enabled = false;
            LogInfo("New "+_TimerName+" Timer Created...",this);



        }
        public IISTimer(string TimerName)
        {
           
            _TimerName = TimerName;
            _Infinite = false;
            _Interval = 0;
            _Enabled = false;
            LogInfo("New " + _TimerName + " Timer Created...",this);

        }
         void BeginRemove()
        {
            Destroying = true;
            HttpRuntime.Cache.Remove(this.SyncKey);
        }
        /// <summary>
        /// The Method Enables Timer CallBack
        /// </summary>
        public void Enable()
        {
            LogInfo(_TimerName + " timer status was set as ENABLED ...",this);
            _Enabled = true;
            HttpRuntime.Cache.Add(SyncKey, new Object(), null, Cache.NoAbsoluteExpiration, TimeSpan.FromMilliseconds(_Interval), CacheItemPriority.NotRemovable, this.OnTimerRemoveCallBack);
        }
        /// <summary>
        /// The Method Disables Timer CallBack
        /// </summary>
        public void Disable()
        {
            LogInfo(_TimerName + " timer status was set as DISABLED ...",this);
            _Enabled = false;
        }
        /// <summary>
        /// The unique name of the timer
        /// </summary>
        public string Name
        {
            get
            {
                return _TimerName;
            }

        }
        /// <summary>
        /// return s the status of the timer(stopper) Enabled(true) or Disabled(false)
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return _Enabled;
            }
        }
        /// <summary>
        /// True - if timer should work as WindowsTimer
        /// False - if timer should work as onetime stopper
        /// </summary>
        public bool Infinite
        {
            set
            {
                _Infinite = value;
            }
            get
            {
                return _Infinite;
            }
        }
        /// <summary>
        /// The interval in milliseconds for timer CallBack call
        /// </summary>
        public long Interval
        {
            set
            {
                if (value < 0)
                    _Interval = 0;
                else
                    _Interval = value;
            }
            get
            {
                return _Interval;
            }
        }
        /// <summary>
        /// The method adds the CallBack delegate of TimerCallBack
        /// </summary>
        /// <param name="CallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]</param>
        public void AddCallBack(TimerCallBack CallBack)
        {
            LogInfo("Add Timer CallBack :" + CallBack.Method.Name,this);
            _OnTimerCall += CallBack;
        }
        /// <summary>
        /// The method adds exception CallBack delegate of ExceptionOccured
        /// </summary>
        /// <param name="ExceptionCallBack">ExceptionOccured [void ExcpetionOccured(Exception exp,string methood)] </param>
        public void AddExceptionCallBack(ExcpetionOccured ExceptionCallBack)
        {
            LogInfo("Add Exception CallBack :" + ExceptionCallBack.Method.Name,this);
            _OnException += ExceptionCallBack;
        }
#region Static Methods
        private static bool SetTimerProperties(string TimerName,long Interval,bool Infinite,TimerCallBack CallBack,ExcpetionOccured ExceptionCallBack,out Exception FailureCause)
        {
            Monitor.Enter(objSync);
            {
                IISTimer timer = null;
                LogInfo("Set Timer Properties " + TimerName + "...",null);
                try
                {
                    timer = HttpRuntime.Cache[_Prefix+TimerName] as IISTimer;
                    
                    if (timer == null)
                    {
                        timer = new IISTimer(TimerName);
                        timer.SyncKey = _Prefix + TimerName + ".sync";
                        timer.Interval = Interval;
                        timer.Infinite = Infinite;
                        if (CallBack != null)
                            timer.AddCallBack(CallBack);
                        if (ExceptionCallBack != null)
                            timer.AddExceptionCallBack(ExceptionCallBack);
                        timer.Enable();

                        HttpRuntime.Cache.Add(_Prefix + TimerName,timer,null,Cache.NoAbsoluteExpiration,Cache.NoSlidingExpiration ,CacheItemPriority.NotRemovable ,null);
                    }
                    else
                    {
                        timer.Disable();
                        HttpRuntime.Cache.Remove(_Prefix+TimerName);
                        timer.Interval = Interval;
                        timer.Infinite = Infinite;
                        if (CallBack != null)
                            timer.AddCallBack(CallBack);
                        if (ExceptionCallBack != null)
                            timer.AddExceptionCallBack(ExceptionCallBack);
                    }

                    HttpContext.Current.Cache.Add(timer.SyncKey, new object(), null,Cache.NoAbsoluteExpiration ,TimeSpan.FromMilliseconds(Interval), CacheItemPriority.NotRemovable, timer.OnTimerRemoveCallBack);
                    FailureCause = new Tihnut.Web.Timer.NoException();
                }
                catch (Exception exp)
                {
                    LogError(exp,null);
                    FailureCause = exp;
                    return false;
                }
                LogInfo("Properties Set Successfully...",null);
            }
            Monitor.Exit(objSync);
            return true;
        }
        /// <summary>
        /// The method sets new time or chnages properties of already existing timer
        /// </summary>
        /// <param name="TimerName"> string - The unique name of the timer</param>
        /// <param name="Interval">long - The interval between timer CallBacks call in milliseconds</param>
        /// <param name="Infinite">bool - whether the timer should be infinite(true) or not (false)</param>
        /// <param name="CallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>
        /// <param name="FailureCause">returns Exception, which occured while adding the timer. In a case of success i will return Exception of type NoException</param>
        /// <returns>true whether operation succeded, false not </returns>
       public static bool SetTimer(string TimerName,long Interval,TimerCallBack CallBack,out Exception FailureCause)
       {
           LogInfo("***\tAddTimer " + TimerName + "...",null);
           bool opResult = false;
           
           {
              
               opResult = SetTimerProperties(TimerName, Interval, true, CallBack, null, out FailureCause);
           }
           
           return opResult;
           
       }
        //
        /// <summary>
       /// The method sets new time or chnages properties of already existing timer
        /// </summary>
       /// <param name="TimerName">string - The unique name of the timer</param>
       /// <param name="Interval">long - The interval between timer CallBacks call in milliseconds</param>
       /// <param name="Infinite">bool - whether the timer should be infinite(true) or not (false)</param>
       /// <param name="CallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>
       /// <param name="ExceptionCallBack">ExceptionCallBack [void ExcpetionOccured(Exception exp,string methood)]-the delgate called when any exception occured durring timer lifetime</param>
        /// <param name="FailureCause">Exception - Returns Exception in a case an exception occured,otherwise it returns NoException</param>
        /// <returns>bool - returns true in a case of success,otherwise false</returns>
        public static bool SetTimer(string TimerName, long Interval,  TimerCallBack CallBack,ExcpetionOccured ExceptionCallBack,out Exception FailureCause)
        {
            LogInfo("Add Timer " + TimerName + "...",null);
            bool opResult = false;
           
            {
                opResult = SetTimerProperties(TimerName, Interval, true, CallBack, ExceptionCallBack, out FailureCause);
            }
           
            return opResult;

        }
        /// <summary>
        /// The methods sets onece an interval of time , afterwards the  CallBack method will be called
        /// </summary>
        /// <param name="TimerName">string - The unique name of the timer</param>
        /// <param name="Interval">long - The interval between timer CallBacks call in milliseconds</param>
        /// <param name="CallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>
        /// <param name="ExceptionCallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>
        /// <param name="FailureCause">Exception - Returns Exception in a case an exception occured,otherwise it returns NoException</param>
        /// <returns>bool - returns true in a case of success,otherwise false</returns>
        public static bool SetStopper(string TimerName, long Interval,  TimerCallBack CallBack, ExcpetionOccured ExceptionCallBack,out Exception FailureCause)
        {
            LogInfo("Add Stopper " + TimerName + "...",null);
            bool opResult = false;
            
            {
                opResult = SetTimerProperties(TimerName, Interval, false, CallBack, ExceptionCallBack, out FailureCause);
            }
            

            return opResult;

        }
        /// <summary>
        /// The methods sets onece an interval of time , afterwards the  CallBack method will be called
        /// </summary>
        /// <param name="TimerName">string - The unique name of the timer</param>
        /// <param name="Interval">long - The interval between timer CallBacks call in milliseconds</param>
        /// <param name="CallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>
        /// <param name="ExceptionCallBack">TimerCallBack [void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]-</param>       
        /// <returns>bool - returns true in a case of success,otherwise false</returns>
        public static bool SetStopper(string TimerName, long Interval, TimerCallBack CallBack,out Exception  FailureCause)
        {
            LogInfo("Add Stopper " + TimerName + "...",null);
            bool opResult;
           
            {
                opResult = SetTimerProperties(TimerName, Interval, false, CallBack, null, out FailureCause);
            }
           
            return opResult;
        }
       /// <summary>
       /// The method returns IISTimer by its unique name.If timer does not exists , the method will return null.
       /// </summary>
       /// <param name="TimerName"></param>
       /// <returns></returns>
        public static IISTimer GetTimer(string TimerName)
        {
            IISTimer tmr = null;

            Monitor.Enter(objSync);
            {
                
                tmr =HttpRuntime.Cache.Get(_Prefix+TimerName) as IISTimer;
            }
            Monitor.Exit(objSync);

            
            return tmr;

        }
        /// <summary>
        /// The method returns true , if the IISTimer exists , otherwise false.
        /// </summary>
        /// <param name="TimerName">Unique timer(stopper) name</param>
        /// <returns></returns>
        public static bool Exists(string TimerName)
        {
            if (String.IsNullOrEmpty(TimerName))
                return false;
            if (IISTimer.GetTimer(TimerName) != null)
                return true;
            return false;
        }
        /// <summary>
        /// The method removes timer by its unique name. Returns true if everything ok, otherwise it returns false.
        /// </summary>
        /// <param name="TimerName">Unique timer(stopper) name</param>
        /// <returns></returns>
        public static bool RemoveTimer(string TimerName)
        {
            LogInfo("Start removing timer " + TimerName,null);
            Monitor.Enter(objSync);
            bool result = false;
            try
            {
                IISTimer tmr = HttpRuntime.Cache[_Prefix + TimerName] as IISTimer;
                LogInfo("Removing timer " + TimerName, tmr);
                tmr.BeginRemove();
                HttpRuntime.Cache.Remove(_Prefix + TimerName);
                result = true;
            }
            catch (Exception exp)
            {
                LogError(exp,null);
            }
            Monitor.Exit(objSync);
            LogInfo("Finished removing timer " + TimerName,null);
            return result;

        }
#endregion
        /// <summary>
        /// The method sets new Timer CallBack method.The previous one will be lost.
        /// </summary>
        /// <param name="CallBack">[void TimerCallBack(DateTime time,string TimerName,IISTimer timer)]</param>
        public void ResetTimerCallBack(TimerCallBack CallBack)
        {
            this._OnTimerCall = CallBack;
        }
        /// <summary>
        /// The method sets new Excpetion CallBack method.The previous one will be lost.
        /// </summary>
        /// <param name="ExceptionCallBack">void ExcpetionOccured(Exception exp,string methood)</param>
        public void ResetExceptionCallBack(ExcpetionOccured ExceptionCallBack)
        {
            _OnException = ExceptionCallBack;
        }
        /// <summary>
        /// The method returns the IISTimer instance by unique name, given at the creation.
        /// </summary>
        /// <param name="TimerName">string - The unique timer name</param>
        /// <returns></returns>
        
#region Log Methods
        private static void LogInfo(string LogEntry,IISTimer timer)
        {
            if (_IsLogEnabled)
            {
                string uid = timer != null ? timer.UID.ToString("B") : "UNKNOWN";
                try
                {
                    File.AppendAllText(_LogFileName, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.ms tt") + "\tINFO\t" + uid+"\t"+LogEntry + "\r\n");

                }
                catch { }
            }
        }
        private static void LogWarning(string LogEntry,IISTimer timer)
        {
            if (_IsLogEnabled)
            {

                string uid = timer != null ? timer.UID.ToString("B") : "UNKNOWN"; try
                {
                    File.AppendAllText(_LogFileName, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.ms tt") + "\tWARNING\t" +uid+"\t"+ LogEntry + "\n\r");

                }
                catch { }
            }
        }
        private static void LogError(Exception e,IISTimer timer)
        {
            StringBuilder mes = new StringBuilder();
            mes.Append("Exception :" + e.Message+"\n");
            mes.Append("\tStack Trace :" + e.StackTrace+"\n");
            if (e.InnerException != null)
            {
                mes.Append("\tInner Exception :" +e.InnerException.Message + "\n");
                mes.Append("\tStack Trace :" + e.InnerException.StackTrace + "\n");
            }
            if (_IsLogEnabled)
            {
                try
                {
                    string uid = timer != null ? timer.UID.ToString("B") : "UNKNOWN";
                    File.AppendAllText(_LogFileName,"*****\t"+ DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.ms tt") + "\tEXCEPTION\t" + uid+"\t"+mes.ToString() + "\n\r");

                }
                catch { }
            }
        }
        /// <summary>
        /// The method starts logging of adding ,calling callbaks and removing of timers
        /// </summary>
        /// <param name="LogFileName">Path of Log file.</param>
        public static void StartLogging(string LogFileName)
        {
            if (_IsLogEnabled)
                return;
            _LogFileName = LogFileName;
            _IsLogEnabled = true;
            LogInfo("\t*****************************************",null);
            LogInfo("IISTimer 1.1.0.2 Log Started...",null);

        }
        /// <summary>
        /// Stop the logging
        /// </summary>
        public static void StopLogging()
        {
            LogInfo("IISTimer Log Stoppped...",null);
            _IsLogEnabled = false;
        }

#endregion
        
        public void OnTimerRemoveCallBack(string key, object obj, CacheItemRemovedReason reason)
        {
            try
            {
                if (Destroying)
                    return;
                Monitor.Enter(objSync);
               
                    if (_Enabled)
                    {
                        if (_OnTimerCall != null)
                        {
                            try
                            {
                                LogInfo("Before " + _TimerName + " CallBack method call...",null);
                                _OnTimerCall(DateTime.Now, _TimerName, this);
                                LogInfo("After " + _TimerName + " CallBack method call...",null);
                            }
                            catch (Exception exp)
                            {
                                LogError(exp,null);
                                if (_OnException != null)
                                    try
                                    {
                                        LogInfo("Before Exception CallBack method call...",null);
                                        _OnException(exp, "OnTimerCallBack");
                                        LogInfo("After Exception CallBack method call...",null);
                                    }
                                    catch (Exception e) { LogError(e,null); }
                            }
                        }
                        if (_Infinite)
                        {
                            HttpRuntime.Cache.Add(SyncKey, new Object(), null, Cache.NoAbsoluteExpiration, TimeSpan.FromMilliseconds(_Interval), CacheItemPriority.NotRemovable, this.OnTimerRemoveCallBack);
                        }
                        else
                        {
                            try
                            {
                                HttpRuntime.Cache.Remove(_Prefix + _TimerName);
                            }
                            catch (Exception exp)
                            {
                                LogError(exp, null);
                            }
                        }
                        
                    }
               
                
                Monitor.Exit(objSync);
            }
            catch (Exception exp)
            {
                LogWarning("Cache Remove CallBack Failure ....",null);
                LogError(exp,null);
                if (_OnException != null)
                    try
                    {
                        LogInfo("Before Exception CallBack method call...",null);
                        _OnException(exp, "OnTimerCallBack");
                        LogInfo("After Exception CallBack method call...",null);
                    }
                    catch (Exception e) { LogError(e,null); }
            }
        }
    }
}
